vue-element-admin 后台管理框架集成

必要的准备工作

  1. ES6特性的基本了解
  2. Vue2.0框架
    1. 关于Vue实例的生命周期
    2. 关于Vue实例的生命周期created和mounted的区别
    3. Vuejs中关于computed、methods、watch的区别
    4. Vue2 组件间通信全方案
  3. element-ui
  4. axios 请求接口
    1. axios post方式传递参数,请求参数格式化
  5. vue-router 官方路由组件
  6. Mock.js 拦截ajax请求生成假数据
  7. vue-element-admin官方文档

环境的搭建

Node.js和Git的安装

  1. https://nodejs.org/en/ (开代理访问速度更快)
  2. https://git-scm.com/downloads Windows下的Git工具

VSCode自带的集成终端powershell在npm全局安装需要管理员权限

  1. 踩坑后的解决方案:https://segmentfault.com/q/1010000009166404

VSCode插件推荐及配置相关

必备插件

  1. ESLint
    插件化的 javascript 代码检测工具
  2. Prettier
    代码美化插件
  3. Vetur
    vue 插件 语法高亮、智能感知、Emmet 等
  4. Gitlens
    git 日志插件

推荐插件

  1. Better Comments
    可配置的代码注释插件
  2. Auto Rename Tag
    自动重命名 HTML/XML 标签
  3. Open in browser
    在默认浏览器打开 HTML 文件
  4. Path Intellisense
    自动路劲补全
  5. Polacode
    代码截图
  6. Setting Sync
    同步 VScode 插件配置
  7. VSCode Great Icons
    文档类型图标
  8. Dracula Official
    暗色系主题
  9. Bracket Pair Colorizer
    让括号拥有独立的颜色,易于区分

推荐的插件配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
{
"editor.fontSize": 20,
"editor.multiCursorModifier": "ctrlCmd",
"editor.snippetSuggestions": "top",
"explorer.confirmDelete": false,
"files.associations": {
"*.tpl": "html"
},
"editor.wordWrap": "on",
"editor.renderIndentGuides": true,
"emmet.triggerExpansionOnTab": true,
"gitlens.advanced.messages": {
"suppressCommitHasNoPreviousCommitWarning": false,
"suppressCommitNotFoundWarning": false,
"suppressFileNotUnderSourceControlWarning": false,
"suppressGitVersionWarning": false,
"suppressLineUncommittedWarning": false,
"suppressNoRepositoryWarning": false,
"suppressResultsExplorerNotice": false,
"suppressShowKeyBindingsNotice": true,
"suppressUpdateNotice": false,
"suppressWelcomeNotice": true
},
// "editor.formatOnSave": true,
"[javascript]": {
"editor.formatOnSave": true
},
"html.format.enable": false,
"gitlens.gitExplorer.files.layout": "tree",
"git.enableSmartCommit": true,
"emmet.includeLanguages": {
"markdown": "html"
},
"emmet.excludeLanguages": [""],
"eslint.enable": false,
"workbench.startupEditor": "newUntitledFile",
// "prettier.spaceBeforeFunctionParen": true,
"editor.tabSize": 2,
"eslint.autoFixOnSave": true,
"git.autofetch": true,
"editor.formatOnPaste": false,
"prettier.tabWidth": 2,
"prettier.singleQuote": true,
"prettier.trailingComma": "none",
"prettier.semi": false,
"prettier.useTabs": false,
"vetur.format.defaultFormatter.js": "prettier",
"vetur.format.defaultFormatterOptions": {
"js-beautify-html": {
"wrap_attributes": "force-aligned"
}
},
"editor.insertSpaces": true,
"editor.detectIndentation": true,
"eslint.validate": [
"javascript",
"javascriptreact",
"html",
"vue",
{
"language": "vue",
"autoFix": true
}
],
"eslint.options": {
"plugins": ["html"]
},
"editor.formatOnSave": true,
// "prettier.javascriptEnable": [
// "javascript",
// "javascriptreact"
// ],
"vetur.format.defaultFormatter.html": "js-beautify-html",
"emmet.syntaxProfiles": {
"vue-html": "html",
"vue": "html"
},
"gitlens.keymap": "alternate",
"explorer.confirmDragAndDrop": false,
"search.location": "sidebar",
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/bower_components": true,
"**/jspm_packages": true
},
"extensions.autoUpdate": true,
"gitlens.historyExplorer.enabled": true,
"markdown.preview.breaks": true
}

把以上配置放入VScode IDE 下 .vscode/settings.json工作区设置里

准备开始搬砖

补充关于路径@占位符:webpack.base.conf.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module.exports = {
context: path.resolve(__dirname, '../'),
entry: {
app: './src/main.js'
},
output: {
path: config.build.assetsRoot,
filename: '[name].js',
publicPath: process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'@': resolve('src'),
}
}

webpack配置的路径,@占位符表示src。

所有的功能实现都从路由开始

路由和侧边栏是组织起一个后台应用的关键骨架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
path: '/getClassReport',
component: Layout,
redirect: '/permission/index', //重定向地址,在面包屑中点击会重定向去的地址
hidden: true, // 不在侧边栏线上
alwaysShow: true, //一直显示根路由
meta: { roles: ['admin','editor'] }, //你可以在根路由设置权限,这样它下面所以的子路由都继承了这个权限
children: [{
path: 'index',
component: ()=>import('permission/index'),
name: 'permission',
meta: {
title: 'permission',
icon: 'lock', //图标
role: ['admin','editor'], //或者你可以给每一个子路由设置自己的权限
noCache: true // 不会被 <keep-alive> 缓存
}
}]
}

关于keep-alive:是Vue的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM。

使用Mock数据进行开发

基础路由侧边栏的配置

@/router/index.vue

1
2
3
4
5
6
7
8
9
10
11
12
{
path: '/pi',
component: Layout,
children: [
{
path: 'index',
component: () => import('@/views/pi/index'),
name: 'raspberrypi',
meta: { title: 'Raspberry Pi', icon: 'raspberrypi' }
}
]
}

创建API请求方法

在 @api 文件夹下创建API

@/api/pi.js

1
2
3
4
5
6
7
8
import request from '@/utils/request'

export function getGPIOStatus() {
return request({
url: '/pi/getGPIOStatus',
method: 'get'
})
}

使用Mock.js拦截请求

@/mock/index.js添加拦截规则

1
2
import raspberryPiAPI from './pi' //导入创建的API
Mock.mock(/\/pi\/getGPIOStatus/, 'get', raspberryPiAPI.getGPIOStatus)

添加对拦截接口的Mock数据进行生成

新建Mock生成规则文件@/mock/pi.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Mock from 'mockjs'

const GPIOSize = 14
const GPIO = []

for (let item = 0; item < GPIOSize; item++) {
GPIO.push(Mock.mock(
{
'gpio': '@increment',
'name': Mock.Random.csentence(1, 3),
'switch': Mock.Random.bool(3, 20, true)
}
))
}

export default {
getGPIOStatus: () => {
return {
//把生成组装好的Mock数据导出
total: GPIO.length,
items: GPIO
}
}
}

创建路由所指向对应的业务组件

@/views/pi/index.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<template>
<div class="tab-container">
<el-table :data="tabData" //表格绑定tabData数据
border // 一些样式调整
fit
highlight-current-row
style="width:100%">
<el-table-column align="center" // 表格文字居中
prop="gpio" //tabData.gpio 字段
label="GPIO"> // 标签
</el-table-column>
<el-table-column align="center"
prop="name"
label="作用">
</el-table-column>
<el-table-column align="center"
prop="switch"
label="状态">

<template slot-scope="scope"> https://segmentfault.com/a/1190000012996217
<!--通过(scope.row)拿到当前行数据-->
<el-tag v-if=scope.row.switch //当检查改行数据switch状态来渲染标签
type="success">打开状态</el-tag>
<el-tag v-else
type="danger">关闭状态</el-tag>
</template>
</el-table-column>
<el-table-column align="center"
prop="switch"
label="开关">
<template slot-scope="scope">
<el-switch v-model="scope.row.switch"
active-color="#13ce66" //激活状态的颜色
inactive-color="#ff4949" //闭合状态的颜色
@change='change($event,scope.$index,scope)'></el-switch>
// change事件 传入当前事件的值,和插槽索引,和插槽详细参数
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
//导入请求API
import { getGPIOStatus } from '@/api/pi'
export default {
data() {
return {
tabData: null
}
},
created() {
//在创建钩子上调用请求接口方法
this.getGPIOData()
},
methods: {
//在参数改变时候在控制台打印日志
change: function($callback, index, scope) {
// $callback在改变事件时候el-switch的状态,index索引
this.$log.debug('switch callback状态', $callback, index, scope)
},
getGPIOData() {
getGPIOStatus().then(response => {
this.tabData = response.data.items
})
}
}
}
</script>

<style scoped>
.tab-container {
margin: 20px;
}
</style>

关于slot-scope:vue原生slot-scope 的值将被用作一个临时变量名,可以接收父组件传过来的值, 而在element中的table对slot-scope的值封装成了一个大的对象,对象里面有属性row(行),column(列),$index(索引),store,所以我们可以通过scope.row拿到对应的值。

使用真实接口进行开发

基础路由侧边栏的配置

@/router/index.vue

1
2
3
4
5
6
7
8
9
10
11
12
{
path: '/getClassReport',
component: Layout,
children: [
{
path: 'index',
component: () => import('@/views/getClassReport/index'),
name: 'getClassReport',
meta: { title: '获取班级报告', icon: 'example' }
}
]
}

每个页面或者模块特定的业务组件则会写在当前 views 下面。如:@/views/article/components/xxx.vue

创建和视图对应的API

在 @api 文件夹下创建API

@api/getClassReport.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//vue-element-admin 封装的请求方法
import request from '@/utils/request'
import qs from 'qs'
export function getClassReport(hwId, classId) {
//格式化传参 原因查看准备工作4-1
const data = qs.stringify({
hwId,
classId
})
return request({
url:
'/homework_middle_customer_service/homeworkReportService/getClassReport',
method: 'post',
data
})
}

创建路由所指向对应的业务组件

在views下创建目录和@views/getClassReport/index.vue父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
<template>
<el-container> // Container 布局容器 方便快速搭建页面的基本结构
<el-header>
<h2 class="hwTitle"
v-if="classData!=null">{{classData.hwTitle}}</h2>// 在classData不为null的时候渲染dom
</el-header>
<el-main>
<el-row>
// 类似于 BootStrap的栅格布局 如果不使用md xs lg sm属性 可以使用:span属性
<el-col :md="12"
:xs="24">
// 自定义子组件 echarts 饼图 自定义属性把classData的返回值传递给子组件
<pieChart :classReportData='classData'></pieChart>
</el-col>
<el-col :md="12"
:xs="24">
<el-card>
//循环classData Map里的key和value 打印出来
<div v-for="(val,key) in classData"
:key="key">
{{key}}----{{val}}</div>
</el-card>
</el-col>
</el-row>
<el-footer>
// 自定义子组件输入框 自定义属性在子组件组件里创建
<getReportInput @put-hw-param='getClassReportData'></getReportInput>
</el-footer>
</el-main>
</el-container>
</template>
<script>
//导入组件
import pieChart from './PieChart'
import getReportInput from './input'
import { getClassReport } from '@/api/getClassReport' // 班级报告API
//http://es6.ruanyifeng.com/?search=import&x=0&y=0#docs/module#export-default-%E5%91%BD%E4%BB%A4
export default {
// name相当于组件的相当于一个全局 ID,https://cn.vuejs.org/v2/api/#name
name: 'getClassReport',
components: { pieChart, getReportInput },
data() {
// 不使用return包裹的数据会在项目的全局可见,会造成变量污染
// 使用return包裹后数据中变量只在当前组件中生效,不会影响其他组件。
return {
classData: null
}
},
//created的时候数据已经和data属性进行绑定(放在data中的属性当值发生改变的同时,视图也会改变)。
created() {
//默认传参
this.getClassReportData(
'4ac6aa86-4373-4c50-873e-9922edcce57d',
'1500000200030607345'
)
},
methods: {
//如果传参为空直接return
getClassReportData(hwId, classId) {
if (hwId == null || classId == null) {
return
}
// 请求接口
return getClassReport(hwId, classId).then(response => {
//如果返回值不等于200弹出错误消息提示框
if (response.data.code !== 200) {
this.$message(response.data.info)
return
}
//在全局配置了日志组件vuejs-logger
this.$log.debug('response', response.data)
//把请求到的数据放入data里
this.classData = response.data.result
})
}
}
}
</script>
// 全局的 @/style 放置一下全局公用的样式,每一个页面的样式就写在当前 views下面,加上scoped 或者命名空间,避免造成全局的样式污染。
<style scoped>
h2.hwTitle {
text-align: center;
color: lightsalmon;
}
</style>

创建输入框子组件

@views/getClassReport/input.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<template>
<el-row :gutter="10" // gutter 栅格间隔
justify="center" // flex 布局下的水平排列方式
type="flex">

<el-col :span="4">
<el-input v-model="hwId"
placeholder="作业Id"
change="setHwId"></el-input>
</el-col>
<el-col :span="4">
<el-input v-model="classId"
placeholder="班级Id"
change="setClassId"></el-input>
</el-col>
<el-col :span="4">
<el-button type="primary"
@click="classReportEvent">请求数据</el-button>//点击触发事件
</el-col>
</el-row>
</template>

<script>
export default {
name: 'getReportInput',
data() {
return {
hwId: '',
classId: ''
}
},
methods: {
classReportEvent() {
//通过$emit 实现子组件向父组件通信 属性名称为put-hw-param,把hwId classId传递给父组件
this.$emit('put-hw-param', this.hwId, this.classId)
}
}
}
</script>

创建echarts饼图组件

@views/getClassReport/PieChart.vue

关于Echarts 使用方法需要到http://echarts.baidu.com/充电 ,我也是搬砖而已。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
<template>
<div :class="className"
:style="{height:height,width:width}"></div>
</template>

<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
export default {
props: {
// 从父组件接收的值
classReportData: {
default: null
},
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '400px'
}
},
watch: {
//监测classReportData返回值是否有变动,有变动的初始化echarts图形
classReportData() {
this.initChart()
}
},
data() {
return {
chart: null
}
},
mounted() {
this.__resizeHanlder = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHanlder)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHanlder)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')

this.chart.setOption({
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c} ({d}%)'
},
legend: {
left: 'center',
bottom: '10',
data: ['fullCount', 'goodCount', 'passCount', 'unPassCount']
},
calculable: true,
series: [
{
name: '班级报告',
type: 'pie',
roseType: 'radius',
radius: [15, 95],
center: ['50%', '38%'],
data: [
{ value: this.classReportData.fullCount, name: 'fullCount' },
{ value: this.classReportData.goodCount, name: 'goodCount' },
{ value: this.classReportData.passCount, name: 'passCount' },
{ value: this.classReportData.unPassCount, name: 'unPassCount' }
],
animationEasing: 'cubicInOut',
animationDuration: 2600
}
]
})
}
}
}
</script>

进阶扩展

例: 安装日志组件

https://www.npmjs.com/package/vuejs-logger

在终端输入安装

1
npm install vuejs-logger --save-exact

把日志组件设置成全局组件

main.js引入日志组件

1
2
3
4
5
6
7
8
9
10
11
12
13
import VueLogger from 'vuejs-logger'

const options = {
isEnabled: true,
logLevel : isProduction ? 'error' : 'debug',
stringifyArguments : false,
showLogLevel : true,
showMethodName : true,
separator: '|',
showConsoleColors: true
}
//查配置插件注入到全局方法
Vue.use(VueLogger, options)

在其他组件就可以很简单的使用它来打印一些对象和变量了

1
this.$log.debug('response', response.data)

关于一些第三方组件模块的引用,查看文档内的相关说明

关于icon图标自定义

阿里”爸爸”的庞大的图标库:http://iconfont.cn/

在图标库里搜索你想要的图标,下载该图标SVG文件,然后扔进@/icons

食用方法:

1
<svg-icon icon-class="password" />

Font Awesome图标字体库使用舒服多了。

构建打包

这里打包只是在本地打包 ,没有使用CI进行打包构建。

修改打包环境

/config目录下配置dev.env.js 开发环境地址 或新增其他环境文件

1
2
3
4
5
module.exports = {
NODE_ENV: '"development"',
ENV_CONFIG: '"dev"',
BASE_API: '"https://api-dev"' //替换你的API环境的地址
}

打包命令

1
2
3
4
5
6
7
8
# 打包正式环境
npm run build:prod

# 打包测试环境
npm run build:sit

# 打包开发环境
npm run build:dev

然后在项目根目录下/dist目录生成打包后的静态文件。

然后扔到你的CDN或者nginx服务器里就万事大吉了。